#include "ThreadPool.h"
#include "Constants.h"


#include <algorithm>
#include <mutex>
#include <stdexcept>

namespace ThreadPool {

log4cpp::Category& logger = log4cpp::Category::getInstance(
    std::string(SERVER_LOGGER));

WorkerThread::WorkerThread(ThreadPool& tPool):_threadPool(tPool){

}



void WorkerThread::operator()() {

  Constants::setThreadID(_threadID);

  LOG_DEBUG(_threadID+"WorkerThread::operator->In");

  while(true) {

    TaskPtr task(nullptr);
    {
      std::unique_lock<std::mutex> lk(_threadPool._queueLock);
      //wait for any work in queue
      while(!_threadPool.exit() && _threadPool._taskQueue.empty()) {
        _threadPool._condition.wait(lk);
      }

      //check for exit condition
      if (_threadPool.exit()){
        LOG_DEBUG(_threadID+"WorkerThread::operator->Out Exit flag TRUE");
        return;
      }

      LOG_DEBUG(_threadID+"WorkerThread::operator About to get next Job from Queue");
      task = std::move(_threadPool._taskQueue.front());
      _threadPool._taskQueue.pop_front();

    }//free lock

    try {
      LOG_DEBUG(_threadID+"WorkerThread::operator Invoking run on Job");
      task->run();
    }
    catch(std::exception& ex ) {
      LOG_ERROR(ex.what());
    }

  }//while
  LOG_DEBUG(_threadID+"WorkerThread::operator->Out");
}


ThreadPool::ThreadPool(unsigned int maxThreads):_timeToExit(false) {

  for (unsigned int i=0;i<maxThreads;i++) {
    _threadColl.push_back(std::thread(WorkerThread(*this)));
  }

}

ThreadPool::~ThreadPool() {
  LOG_DEBUG("ThreadPool::~ThreadPool->In");
  _timeToExit = true;
  _condition.notify_all();

  //wait for each thread to finish
  LOG_DEBUG("ThreadPool::~ThreadPool waiting for threads to finish");
  for_each(_threadColl.begin(), _threadColl.end(), [](std::thread& th){th.join();});

  LOG_DEBUG("ThreadPool::~ThreadPool->Out");

}

void ThreadPool::waitForWorkers(){
  for_each(_threadColl.begin(), _threadColl.end(), [](std::thread& th){th.join();});
}

bool ThreadPool::queueEmpty() {

  std::unique_lock<std::mutex> lock(_queueLock);
  bool ret = _taskQueue.empty();
  return ret;
}

void ThreadPool::addTask(TaskPtr ptr){
  LOG_DEBUG("ThreadPool::addTask->In");

  {
    std::unique_lock<std::mutex> lock(_queueLock);
    _taskQueue.push_back(std::move(ptr));
  }
  // wake up one thread
  _condition.notify_one();

  LOG_DEBUG("ThreadPool::addTask->Out");

}

TaskPtr ThreadPool::getNextTask(){

  LOG_DEBUG("ThreadPool::getNextTask->In");

  TaskPtr ret(nullptr);
  {
    std::unique_lock<std::mutex> lock(_queueLock);
    ret= std::move(_taskQueue.front());
    _taskQueue.pop_front();
  }

  LOG_DEBUG("ThreadPool::getNextTask->Out");

  return (std::move(ret));
}

}
